package risoe.syslab.fhnew.controller;

/*
 * Copyright (c) 2012-2014, Technical University of Denmark (DTU)
 * All rights reserved.
 * 
 * The Flexhouse 2.0 platform ("this software") is licensed under the
 * BSD 3-clause license.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   - Neither the name of DTU nor the names of its contributors may be used to
 *     endorse or promote products derived from this software without specific
 *     prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE TECHNICAL UNIVERSITY OF DENMARK BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.w3c.dom.Document;
import risoe.syslab.comm.typebased.interfaces.SmartBuilding;
import risoe.syslab.fhnew.common.RMIUtils;
import risoe.syslab.fhnew.common.SystemConstants;
import risoe.syslab.fhnew.common.SystemConstants.DeviceType;
import risoe.syslab.fhnew.config.BuildingConfig;
import risoe.syslab.fhnew.config.PlatformConfig;
import risoe.syslab.fhnew.core.MonitoredModule;
import risoe.syslab.fhnew.db.LoggerManager;
import risoe.syslab.fhnew.gui.DocumentPreparedListener;
import risoe.syslab.fhnew.gui.UIManager;
import risoe.syslab.fhnew.gui.VisualizationHolder;
import risoe.syslab.fhnew.gui.VisualizationUpdate;
import risoe.syslab.fhnew.hw.Actuator;
import risoe.syslab.fhnew.hw.ProcessImage;
import risoe.syslab.fhnew.hw.Sensor;
import risoe.syslab.model.CompositeMeasurement;
import risoe.syslab.model.CompositeString;
import risoe.syslab.model.flexhouse.ControllerParameter;


/**
 * Manages the communication between controllers(default and plugin) and the processimage.
 * Also serves as a link between the UIManager and controllers.
 * @author M. Svendsen
 */
public class ControllerManager implements ControllerManagerRemoteInterface, MonitoredModule {
	// Constants
	public static final String CONTROLLER_MANAGER_NAME = "Controller_Manager";
	private static final long HEARTBEAT_INTERVAL = 1; //seconds
	
	// Controller manager interface
	public static final String CONTROLLER_MANAGER_INTERFACE_KEY = "controller_manager_interface_name";
	public static final String CONTROLLER_MANAGER_INTERFACE_DEFAULT_NAME = "Controller_Manager_Remote_Interface";
	private String mControllerManagerInterfaceName;
	
	// Smart building interface
	public static final String SMART_BUILDING_INTERFACE_KEY = "external_interface_name";
	public static final String SMART_BUILDING_PORT_KEY = "external_interface_port";
	public static final String SMART_BUILDING_INTERFACE_DEFAULT_NAME = "typebased_SmartBuilding::SmartBuildingRMIServer::";
	public static final String SMART_BUILDING_INTERFACE_DEFAULT_PORT = "1099";
	private String mSmartBuildingName;
	private int mSmartBuildingPort;

	private String errmsg;

	private SmartBuildingInterface mSmartBuildingInterface;
	private long lastHeartbeatCheck;

	// TODO Rollout the smartbuilding interface
	@SuppressWarnings("unused")
	private FlexHouseInterface mFlexHouseInterface;

	// Set for keeping track of which permissions are in use by non-default controllers
	private EnumSet<DeviceType> mControlledTypes = EnumSet.noneOf(DeviceType.class);

	// For logging
	private LoggerManager mLoggerManager;

	// For UI operations
	private UIManager mUiManager;
	private ControllerStateHandler mControllerStateHandler;

	// Controller Parameters fields
	private ConcurrentHashMap<ControllerParameter, CompositeString> mControllerParameters = new ConcurrentHashMap<ControllerParameter, CompositeString>();
	private ConcurrentHashMap<DeviceType, Future<?>> mTimeoutMap = new ConcurrentHashMap<SystemConstants.DeviceType, Future<?>>();

	// Mapping of default controllers to permissions
	private Map<DeviceType, Class<? extends DefaultController>> mDefaultControllerMap;

	// Map of all the registered controllerconfigs
	private ConcurrentHashMap<DeviceType, DefaultController> mActiveDefaultControllerMap = new ConcurrentHashMap<DeviceType, DefaultController>();
	private ConcurrentHashMap<String, ControllerConfig> mControllerConfigMap = new ConcurrentHashMap<String, ControllerConfig>();
	private ProcessImage mProcessImage;
	private BuildingConfig mBuildingConfig;

	// Heartbeat related services
	private final ScheduledExecutorService mExecutor = Executors.newSingleThreadScheduledExecutor();
	private final Runnable mHeartbeatTask = new Runnable() {
		@Override
		public void run() {
			try {
				// Test all plugincontrollers
				lastHeartbeatCheck=System.currentTimeMillis();
				Iterator<ControllerConfig> iterator = mControllerConfigMap.values().iterator();
				while (iterator.hasNext()){
					ControllerConfig config = iterator.next();
					boolean alive = false;
					try {
						alive = config.getControllerInterface().issueHeartBeat();
					} catch (Exception e) {}

					if (!alive){
						// The plugincontroller has been removed. Instantiate a new default controller to take its place
						// Remove the associated visualization
						mUiManager.removeVisualization(config.getName());

						// Log change
						mLoggerManager.getPlatformEventLogger().logEvent(CONTROLLER_MANAGER_NAME, "Plugin Controller Died", config.getName());

						// Create new default controllers to take the place of this plugincontroller
						for (DeviceType type : config.getControlledDeviceTypes()){
							// Remove the permissions used by this plugincontroller
							synchronized (mControlledTypes) {
								mControlledTypes.remove(type);
							}

							if (mDefaultControllerMap.containsKey(type))
								mActiveDefaultControllerMap.put(type, getDefaultController(type));
						}

						// Remove controllerparameters
						for (ControllerParameter param : config.getControllerParameters()){
							mControllerParameters.remove(param);
						}

						// Remove from underlying map
						iterator.remove();
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	};

	/**
	 * Create a new controllermanager
	 * @param the process image which is to be controlled
	 * @param config the BuildingConfig to use
	 * @param mParameterMap 
	 * @throws RemoteException is thrown if the RMI binding fails
	 */
	public ControllerManager(ProcessImage procImage, BuildingConfig config, LoggerManager loggerManager, 
			PlatformConfig platform) throws RemoteException {
		
		// Get configuration parameters
		mControllerManagerInterfaceName = platform.getParameter(CONTROLLER_MANAGER_INTERFACE_KEY, mControllerManagerInterfaceName);
		mSmartBuildingName = platform.getParameter(SMART_BUILDING_INTERFACE_KEY, SMART_BUILDING_INTERFACE_DEFAULT_NAME);
		Integer port = Integer.valueOf(platform.getParameter(SMART_BUILDING_PORT_KEY, SMART_BUILDING_INTERFACE_DEFAULT_PORT));
		mSmartBuildingPort = port != null? port.intValue(): Integer.valueOf(SMART_BUILDING_INTERFACE_DEFAULT_PORT);
		
		mProcessImage = procImage;
		mBuildingConfig = config;
		mLoggerManager = loggerManager;
		lastHeartbeatCheck=System.currentTimeMillis();
		String hostName = platform.getHostName();
		procImage.registerControllerManager(this);

		// Load default controllers
		mDefaultControllerMap = mBuildingConfig.getDefaultControllerMap();

		mSmartBuildingInterface = new SmartBuildingInterface(this, mBuildingConfig, mActiveDefaultControllerMap, 
				mControllerConfigMap, mControllerParameters);

		// Start the RMI server
		Registry registry = RMIUtils.getRegistry();
		ControllerManagerRemoteInterface stub = (ControllerManagerRemoteInterface)UnicastRemoteObject.exportObject(this, 0);
		SmartBuilding stub2 = (SmartBuilding)UnicastRemoteObject.exportObject(mSmartBuildingInterface, mSmartBuildingPort);

		// Bind names and objects.
		registry.rebind(mControllerManagerInterfaceName, stub);
		registry.rebind(mSmartBuildingName+hostName, stub2);

		// Start temporary syslab interface
		if (hostName.equals("flexhouse")) {
			System.out.println("Starting temporary syslab interface");
			mFlexHouseInterface = new FlexHouseInterface(this, mBuildingConfig);
		}

		// Start visualization engine
		mUiManager = new UIManager(loggerManager, platform);

		// Initialize the visualization tasked with showing the current controller states
		mControllerStateHandler = new ControllerStateHandler();

		// Start up default controllers
		for (DeviceType type : mDefaultControllerMap.keySet()){

			// Instantiate default controllers and add to map of active default controllers
			mActiveDefaultControllerMap.put(type, getDefaultController(type));
		}

		// Start the heartbeat
		mExecutor.scheduleAtFixedRate(mHeartbeatTask, 0, HEARTBEAT_INTERVAL, TimeUnit.SECONDS);
		mLoggerManager.getPlatformEventLogger().logEvent(CONTROLLER_MANAGER_NAME, "Initialization", "Ready");
	}

	/**
	 * @return a reference to the LoggerManager instance
	 */
	public LoggerManager getLoggerManager(){
		return mLoggerManager;
	}

	/**
	 * Shuts down the ControllerManager. NB: ONLY call this from the FlexhouseCore upon shutdown of the platform.
	 */
	public void shutdown(){
		mExecutor.shutdownNow();
	}

	/**
	 * Register and start a new controller. 
	 * NOTE: In most cases, the supplied config should contain controlled devicetypes and subscriptions
	 * This method should only be invoked from "registerPluginController()"
	 * @param config a controllerconfig representing the controller to start
	 * @throws InstantiationException is thrown if another plugincontroller has already been granted control of one of the devicetypes,
	 * or if a plugincontroller bearing the same name already exists
	 */
	private void registerControllerInternal(ControllerConfig config) throws InstantiationException{
		// Add the actual config here
		if (!mControllerConfigMap.containsKey(config.getName())){
			// Check and update used devicetypes
			if (config.getControlledDeviceTypes() != null){
				for (DeviceType type : config.getControlledDeviceTypes()){
					synchronized (mControlledTypes) {
						if (mControlledTypes != null){
							if (mControlledTypes.contains(type)){
								// Pass info about the PluginController currently in charge of this DeviceType back to the caller of this function
								for (ControllerConfig conf : mControllerConfigMap.values()){
									if (conf != null && conf.getControlledDeviceTypes().contains(type)){
										String name = conf.getName();
										String owner = conf.getOwner();
										throw new InstantiationException("DeviceType: "+type.name()+" already taken control of by "+name+" owned by "+owner);
									}
								}
							}
						}
					}
					if (!type.mIsActuator){
						// Controller asked for control over a sensor, this is clearly an error
						throw new InstantiationException("Cannot take control of sensor type: "+type.name());
					}
				}
			}

			// Deactivate default controller
			if (config.getControlledDeviceTypes() != null){
				// Name and controlled devicetypes have been accepted - Save devicetypes and stop the appropriate defaultcontroller(s)
				synchronized (mControlledTypes) {
					mControlledTypes.addAll(config.getControlledDeviceTypes());
				}

				// Add new ControllerParameters
				for (ControllerParameter param : config.getControllerParameters()){
					mControllerParameters.put(param, new CompositeString("Unset"));
				}

				for (DeviceType type : config.getControlledDeviceTypes()){
					if (mActiveDefaultControllerMap.containsKey(type)){
						String name = mActiveDefaultControllerMap.get(type).getClass().getSimpleName();

						mActiveDefaultControllerMap.get(type).kill();
						mUiManager.removeVisualization(name);
						mActiveDefaultControllerMap.remove(type);

						// Remove old ControllerParameters
						for (ControllerParameter param : mControllerParameters.keySet()){
							if (param.getControllerName().equals(name)){
								// Remove this
								mControllerParameters.remove(param);
							}
						}
					}
					else{
						// This will most likely signify SYSLAB controlling the house
						Future<?> previousTimeout = mTimeoutMap.get(type);
						if (previousTimeout != null && !previousTimeout.isDone()){
							// Take over control from SYSLAB
							previousTimeout.cancel(false);
							System.out.println("SYSLAB yielded control over: "+type.toString()+" to "+config.getName());
						}
					}


					mLoggerManager.getPlatformEventLogger().logEvent(CONTROLLER_MANAGER_NAME, "Controller changed", config.getName()+" now controls "+type.name());

					// Update UI
					mControllerStateHandler.update(type, config.getName());
				}
			}

			// Log change in controller
			mControllerConfigMap.put(config.getName(), config);

			// Add the visualization associated with this plugincontroller
			VisualizationHolder holder = null;
			if (config.getVisualizationURI() != null) {
				holder = new VisualizationHolder(config.getName(), config.getVisualizationURI());
			}
			else {
				holder = new VisualizationHolder(config.getName(), config.getSerializedVisualization());
			}
			holder.setOnDocumentPreparedListener(config);
			holder.setOnVisualizationEventListener(config);

			// Add SVG event subscriptions
			for (String s : config.getSubscribedIds()){
				holder.listenTo(s);
			}

			mUiManager.addVisualization(holder);
		}
		else{
			throw new InstantiationException("Controller: " +config.getName()+" already in use");
		}
	}

	public void addControllerParameterInternal(ControllerParameter param){
		mControllerParameters.put(param, new CompositeString("Unset"));
	}

	/**
	 * Used to query the value of a sensor by name
	 * @param name of the sensor to query
	 * @return a CompositeMeasurement signifying the current value or null if no such sensor exists
	 */
	public CompositeMeasurement getSensorValueInternal(String name){
		return mProcessImage.getSensorValue(name);
	}

	/**
	 * Used to query the value of an actuator by name
	 * @param name of the actuator to query
	 * @return a CompositeMeasurement signifying the current value or null if no such actuator exists
	 */
	public CompositeMeasurement getActuatorValueInternal(String name){
		return mProcessImage.getActuatorValue(name);
	}

	/**
	 * Used to query the setpoint of an actuator by name
	 * @param name of the actuator to query
	 * @return a CompositeMeasurement signifying the current setpoint or null if no such actuator exists
	 */
	public CompositeMeasurement getActuatorSetpointInternal(String name){
		return mProcessImage.getActuatorSetpoint(name);
	}


	/**
	 * Called by the ProcessImage when a sensor has been updated with a new value.
	 * This method passes the event to any default/plugin controllers that might be
	 * interested in it.
	 * @param s the sensor that has been updated
	 */
	public void sensorUpdated(Sensor s) {
		// Route the messages to any active defaultcontroller
		try {
			for (DefaultController controller : mActiveDefaultControllerMap.values()){
				controller.sensorUpdated(s);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		// Call plugincontrollers through filter
		for (ControllerConfig config : mControllerConfigMap.values()){
			try {
				config.filter(s);
			} catch (Exception e) {
				e.printStackTrace();
				System.out.println("Exception caused by plugincontroller: "+config.getName());
			}
		}
	}

	/**
	 * Called by the ProcessImage when an actuator has been updated with a new value.
	 * This method passes the event to any default/plugin controllers that might be
	 * interested in it.
	 * @param a the actuator that has been updated
	 */
	public void actuatorUpdated(Actuator a) {
		// Route the messages to any active defaultcontroller
		try {
			for (DefaultController controller : mActiveDefaultControllerMap.values()){
				controller.actuatorUpdated(a);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}


		// Call plugincontrollers through filter
		for (ControllerConfig config : mControllerConfigMap.values()){
			try {
				config.filter(a);
			} catch (Exception e) {
				e.printStackTrace();
				System.out.println("Exception caused by plugincontroller: "+config.getName());
			}
		}
	}

	/**
	 * Writes to the processImage from a defaultcontroller requiring no controllerconfig.
	 */
	public void setActuatorDefault(String actuatorName, CompositeMeasurement value){
		// This will only get called if no plugincontroller has taken control of this type of actuator
		mProcessImage.setActuator(actuatorName, value);
	}

	/**
	 * Called when SYSLAB attempts to set an actuator setpoint using the SmartBuildingInterface
	 * @param name a String representing the name of the actuator
	 * @param value a CompositeMeasurement indicating the new setpoint
	 */
	public void setActuatorSyslab(String name, CompositeMeasurement value) {
		// Get actuator type
		DeviceType type = null;
		Actuator act = mProcessImage.getActuatorByName(name);
		if (act != null){
			// Match
			type = act.getType();
			synchronized (mControlledTypes) {
				if (mControlledTypes != null){
					if (mControlledTypes.contains(act.getType())){
						// A PluginController is already in charge of this DeviceType. Find out which one
						String controllerName = "";
						for (ControllerConfig config : mControllerConfigMap.values()){
							if (config.getControlledDeviceTypes().contains(act.getType())){
								controllerName = config.getName();
							}
						}

						throw new IllegalArgumentException("DeviceType: "+act.getType().toString()+
								" already controlled by: "+controllerName);
					}
				}
			}
		}

		// No PluginController is in charge of this DeviceType
		// Cancel any already running timeout for this DeviceType
		Future<?> previousTimeout = mTimeoutMap.get(type);
		if (previousTimeout != null){
			previousTimeout.cancel(false);
		}

		// Schedule a new timeout
		final DeviceType typeFinal = type;
		mTimeoutMap.put(type, mExecutor.schedule(new Runnable() {

			@Override
			public void run() {
				// If this function is called, it means that the timeout has occured and the 
				// applicable DefaultController should be swapped back in
				mActiveDefaultControllerMap.put(typeFinal, getDefaultController(typeFinal));
				System.out.println("SYSLAB has relinquished control of: "+typeFinal.toString());
			}
		}, SmartBuildingInterface.SYSLAB_TIMEOUT_MINUTES, TimeUnit.MINUTES));

		// Deactivate the applicable DefaultController
		// Check if a default controller is in place
		if (mActiveDefaultControllerMap.containsKey(type)){
			// Remove the default controller
			mActiveDefaultControllerMap.get(type).kill();
			mUiManager.removeVisualization(mActiveDefaultControllerMap.get(type).getClass().getSimpleName());
			mActiveDefaultControllerMap.remove(type);

			// Remove old ControllerParameters
			for (ControllerParameter param : mControllerParameters.keySet()){
				if (param.getControllerName().equals(name)){
					// Remove this
					mControllerParameters.remove(param);
				}
			}

			getLoggerManager().getPlatformEventLogger().logEvent(ControllerManager.CONTROLLER_MANAGER_NAME, 
					"Controller changed", "SYSLAB now controls "+type.toString());
			System.out.println("SYSLAB given control of: "+type.toString()+" for "+SmartBuildingInterface.SYSLAB_TIMEOUT_MINUTES+" minutes");

			// Update UI
			mControllerStateHandler.update(type, "SYSLAB");
		}

		// Finally - execute the command!
		mProcessImage.setActuator(name, value);
	}

	/**
	 * Writes to the processimage using the values contained in the controllerconfig. This method is called automatically
	 * when calling the setActuator() method from a controller that extends PluginController.
	 * @param config the controllerconfig to check controlled devicetypes
	 * @param actuatorName the name of the actuator to which this write must be directed
	 * @param value the value to write
	 */
	public void setActuator(ControllerConfig config, String actuatorName, CompositeMeasurement value) throws IllegalArgumentException{
		if (config == null || actuatorName == null || value == null){
			throw new IllegalArgumentException("Invalid Argument! Check name of actuator and validity of CompositeMeasurement");
		}

		// Check if the controller calling this function has announced control of the actuator in question
		DeviceType type = mProcessImage.getActuatorType(actuatorName);
		if (type != null){
			if (config.getControlledDeviceTypes().contains(type)){
				// This controller has control over the specific devicetype, allow it to write to the processImage
				mProcessImage.setActuator(actuatorName, value);
			}
			else{
				// Controller tried writing to an actuator outside its allowed range.
				mLoggerManager.getPlatformEventLogger().logEvent(CONTROLLER_MANAGER_NAME, "Warning", "PluginController "
						+config.getName()+" attempted a write with missing permission: "+type.name());
				throw new IllegalArgumentException("Missing control permission for DeviceType: "+type.toString());
			}
		}
	}

	/**
	 * Convenience method for looking up the appropriate DefaultController for a certain
	 * type of device and instantiating it using reflection.
	 * @param type the DeviceType for which a new DefaultController should be created
	 * @return a reference to the newly instantiated DefaultController
	 */
	private DefaultController getDefaultController(DeviceType type){		
		// Instantiate this controller using reflection
		DefaultController controller = null;
		try {
			Constructor<?> ctor = mDefaultControllerMap.get(type).getConstructor(ControllerManager.class, BuildingConfig.class);
			controller = (DefaultController) ctor.newInstance(this, mBuildingConfig);
		} catch (NoSuchMethodException e){
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}

		// Log Controller change
		mLoggerManager.getPlatformEventLogger().logEvent(CONTROLLER_MANAGER_NAME, "Controller changed", controller.getClass().getSimpleName()+" now controls "+type.name());
		VisualizationHolder holder = controller.getVisualization();
		if (holder != null){
			mUiManager.addVisualization(holder);
		}

		// Add appropriate ControllerParameters
		for (ControllerParameter param : controller.getControllerParameters()){
			mControllerParameters.put(param, new CompositeString("Unset"));
		}

		// Update UI
		if (mControllerStateHandler != null){
			mControllerStateHandler.update(type, controller.getClass().getSimpleName());
		}

		return controller;
	}

	/**
	 * Method used for passing incoming VisualizationUpdates on to the UIManager
	 * @param update an incoming VisualizationUpdate
	 */
	public void executeVisualizationUpdateInternal(VisualizationUpdate update){
		mUiManager.executeVisualizationCommand(update);
	}


	/**
	 * The visualization tasked with showing which controllers are in control of the various
	 * types of devices in the PowerFlexhouse. This visualization is managed directly by the
	 * Controller Manager.
	 * @author M. Svendsen
	 */
	private class ControllerStateHandler implements DocumentPreparedListener{
		private final URI SVG_PATH = new File("media/controllerStateVis.svg").toURI();

		// Mapping of controllable devicetypes to document IDs
		private Map<DeviceType, String> mDeviceIdMap;

		private boolean mDocumentPrepared = false;

		/**
		 * Construct a controllerStateVisualization. Requires a list of the currently active default controllers
		 * @param mdefaultcontrollermap
		 */
		public ControllerStateHandler() {		
			Map<DeviceType, String> aMap = new HashMap<DeviceType, String>();
			aMap.put(DeviceType.PowerControlledHeater, "text3021");
			aMap.put(DeviceType.DimmableLight, "text3021-1");
			aMap.put(DeviceType.Switch, "text3021-1-9");
			aMap.put(DeviceType.Waterboiler, "text3021-1-2");
			mDeviceIdMap = Collections.unmodifiableMap(aMap);

			VisualizationHolder holder = new VisualizationHolder(CONTROLLER_MANAGER_NAME, SVG_PATH);
			holder.setOnDocumentPreparedListener(this);

			mUiManager.addVisualization(holder);
		}

		@Override
		public void onDocumentReady(Document document) {
			// Document is prepared. Update canvas with the entries in the initialmap.
			VisualizationUpdate update = new VisualizationUpdate(CONTROLLER_MANAGER_NAME);
			for (DeviceType type: mDefaultControllerMap.keySet()){
				String name = mDefaultControllerMap.get(type).getSimpleName();

				if (name != null){
					update.addCommand(mDeviceIdMap.get(type), name);
				}
			}
			executeVisualizationUpdateInternal(update);

			mDocumentPrepared = true;
		}

		/**
		 * Used by the ControllerManager to schedule new updates on the UI thread
		 * @param type the devicetype which is now controlled by a new controller
		 * @param newName the name of the new controller
		 */
		public void update(final DeviceType type, final String newName){
			if (!mDocumentPrepared) return;

			VisualizationUpdate update = new VisualizationUpdate(CONTROLLER_MANAGER_NAME);
			update.addCommand(mDeviceIdMap.get(type), newName);
			executeVisualizationUpdateInternal(update);
		}
	}



	/**
	 * RMI INTERFACE TO PLUGINCONTROLLERS
	 */
	@Override
	public void log(String source, String type, String message)
			throws RemoteException {
		mLoggerManager.getHouseEventLogger().logEvent(source, type, message);
	}

	@Override
	public void registerPluginController(String name) throws RemoteException, InstantiationException {
		ControllerConfig config = null;

		// Establish RMI connection and bind it to the controllerconfig
		try {
			Registry registry = LocateRegistry.getRegistry(SystemConstants.DEFAULT_RMIPORT);
			PluginControllerInterface intf = (PluginControllerInterface) registry.lookup(name);

			// Get the config used by this controller
			config = intf.getControllerConfig();

			// Bind the remoteinterface to the local controllerconfig object
			config.setRemoteController(intf);
		} catch (RemoteException e) {
			e.printStackTrace();
		} catch (NotBoundException e) {
			e.printStackTrace();
		}

		// Register the plugincontroller associated with this controllerconfig
		registerControllerInternal(config);
	}

	@Override
	public BuildingConfig getBuildingConfig() throws RemoteException {
		return mBuildingConfig;
	}

	@Override
	public void setActuator(String controllerName, String actuatorName, CompositeMeasurement value) throws RemoteException {
		// Get the controllerconfig for this controller
		ControllerConfig config = mControllerConfigMap.get(controllerName);
		ControllerManager.this.setActuator(config, actuatorName, value);
	}

	@Override
	public Actuator[] getActuatorStates() throws RemoteException {
		return mProcessImage.getActuators();
	}

	@Override
	public CompositeMeasurement getSensorValue(String name)	throws RemoteException {
		return getSensorValueInternal(name);
	}

	@Override
	public CompositeMeasurement getActuatorValue(String name) throws RemoteException{
		return getActuatorValueInternal(name);
	}

	@Override
	public CompositeMeasurement getActuatorSetpoint(String name) throws RemoteException {
		return getActuatorSetpointInternal(name);
	}

	@Override
	public void executeVisualizationUpdate(VisualizationUpdate command)
			throws RemoteException {
		executeVisualizationUpdateInternal(command);
	}

	@Override
	public Status getStatus() {
		if ((System.currentTimeMillis()-lastHeartbeatCheck)>(2000*HEARTBEAT_INTERVAL)) {
			errmsg="ControllerManager: Heartbeat thread died.";
			return MonitoredModule.Status.Fault;
		}
		errmsg="";
		return MonitoredModule.Status.Running;
	}

	@Override
	public String getFaultMessage() {
		return errmsg;
	}
}
